package net.ib.mtalk.ui.attachment.scribble;

import java.util.LinkedList;
import java.util.NoSuchElementException;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;

/**
 * Scribble history and draw Manager.<br>
 * <br>
 * <b>개요</b><br>
 * <br>
 * 획의 정보를 저장하고 있다. 저장한 정보를 Canvas에 그린다. 저장한 정보를 지우거나 복구한다.<br>
 * <br>
 * <b>구현</b><br>
 * <br>
 * Scribble history의 복원을 위해 Command pattern(query에 대한 기억과 재연)을 사용하고 있다. Redo를 위해 query를 양방향으로 저장한다.
 * ScribbleManager 클래스는 이 모든 query에 대한 저장소이자 관리자이며, 이를 토대로 Canvas에 대한 flush 동작을 지원한다.<br>
 * <br>
 * <b>개선안</b><br>
 * <br>
 * 속도의 개선이 필요하다면 Memento pattern(state에 대한 기억)을 조합할 수 있겠다.
 * 그러나 그 경우 메모리를 투자해야 하며, 어느 정도의 빈도로 state 캐시를 만들지 결정하기 위해 usage 통계가 필요하다.
 * 이상적인 형태는 다음과 같다.<br>
 * <br>
 * 1. 유저가 느리다고 인식할 수 있는 시간적 경계점 Tc를 규정.<br>
 * 2. 모든 query를 저장한다. 또, 복원에 드는 시간이 Tc보다 길어지는 지점마다 state를 저장한다.<br>
 * 3. Undo를 통해 특정 state Sr로 이행해야 할 경우,<br>
 * 3.1. 그 state를 달성하기 전의 저장된 모든 state 중 가장 나중에 만들어진 Sb를 추출.<br>
 * 3.2. Sb로부터 Sr에 도달하기까지의 query list Ql을 추출.<br>
 * 3.3. Sb와 Ql을 이용하여 Sr을 복원.<br>
 * 4. Redo도 거울대칭의 구조를 이용하여 구현한다.<br>
 * 5. 메모리 문제에 대응하기 위하여 특정 횟수나 용량 이상의 history 는 포기하고, 그에 대한 복원은 막는다.<br>
 * @author Arngard_김동희
 */
public class ScribbleManager {

	/**
	 * 한 획의 정보를 담고 있는 클래스. 한 획을 구성하는 요소는 path와 paint이다.
	 * @author Arngard_김동희
	 */
	public class ScribleAtom {
		Path path;
		Paint paint;

		public ScribleAtom(Path path, Paint paint) {
			this.path = path;
			this.paint = paint;
		}
	}

	/**
	 * 그림 기록 스택. 이전에 그렸던 획의 정보들을 저장하는 스택. 지우개도 하나의 획이다.
	 */
	protected LinkedList<ScribleAtom> historyStack = new LinkedList<ScribleAtom>();

	/**
	 * 삭제 기록 스택. 지운 획의 정보들을 저장. 지우개도 하나의 획이다.
	 */
	protected LinkedList<ScribleAtom> recoveryStack = new LinkedList<ScribleAtom>();

	/**
	 * 마지막 draw() 당시의 스택 크기를 저장한다.
	 * 그리기를 호출하면 이 변수를 양의 정수로 세팅한다. 스택을 변경할 때 -1로 세팅해야 한다.
	 */
	protected int drawnSize = -1;	// 초기값에 주의
	
	/**
	 * @return 마지막 draw() 당시의 스택 크기. -1 이면 한 번도 그려지지 않은 것.
	 */
	public int getSize() {
		return drawnSize;
	}

	/**
	 * @return 스택이 현재의 상태에 이른 후 draw()가 그려진 적이 있는지.
	 * 마지막으로 그림 기록 스택이 변한 이후, 그려진 적이 있으면 true.
	 */
	public boolean isDrawn() {
		return drawnSize >= 0;
	}

	/**
	 * @param canvas 그리기 대상이 되는 Canvas
	 * @return 이 그리기에 사용된 그림 기록 스택의 크기. 이 메소드의 수행에 의해 총 몇 개의 획을 그렸는지.
	 */
	public int draw(Canvas canvas) {
		drawnSize = historyStack.size();
		for (ScribleAtom sa : historyStack) {
			canvas.drawPath(sa.path, sa.paint);
		}
		return drawnSize;
	}

	/**
	 * 새 획을 그린다.
	 * 획의 정보를 받아, 그 복사본을 그림 기록 스택에 저장한다.
	 * 이 동작이 이루어질 때마다 삭제 기록 스택은 비워진다.
	 * @param path 새로 넣을 획의 정보
	 * @param paint 새로 넣을 획의 정보
	 * @return 그림 기록 스택의 결과적인 크기
	 */
	public int add(Path path, Paint paint) {
		drawnSize = -1;
		historyStack.addLast(new ScribleAtom(new Path(path), new Paint(paint)));
		recoveryStack.clear();
		return historyStack.size();
	}

	/**
	 * 그렸던 획을 취소한다.
	 * 그림 기록 스택에서 한 획을 삭제한다. 삭제된 획은 그대로 삭제 기록 스택으로 들어간다.
	 * @return 그림 기록 스택의 결과적인 크기
	 * @throws NoSuchElementException 그림 기록 스택에 아무것도 없을 때.
	 */
	public int remove() throws NoSuchElementException {
		drawnSize = -1;
		recoveryStack.addLast(historyStack.removeLast());
		return historyStack.size();
	}

	/**
	 * 지웠던 획 하나를 복구한다.
	 * 삭제 기록 스택에서 한 획을 제거하고 그 획을 그림 기록 스택에 넣는다.
	 * @return 결과의 스택 크기
	 * @throws NoSuchElementException 삭제 기록 스택에 아무것도 없을 때.
	 */
	public int recover() throws NoSuchElementException {
		drawnSize = -1;
		historyStack.addLast(recoveryStack.removeLast());
		return historyStack.size();
	}

}